home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
011
/
extend.arc
/
EXTEND.PAS
Wrap
Pascal/Delphi Source File
|
1987-03-22
|
24KB
|
447 lines
(******************************************************************************
EXTEND.PAS
Version 1.5
August 25, 1986
by Randy Forgaard
CompuServe 70307,521
MS-DOS and PC-DOS Turbo Pascal 3.0 and higher only allow up to 15 files to be
open at the same time, due to limitations in DOS. This file shows you how to
have up to 96 files open simultaneously under DOS 2.0 or 2.1, or 252 files open
simultaneously under DOS 3.0 or higher. Below is a description of how to use
this technique, followed by a technical explanation of the implementation, for
those who are interested.
TO USE THIS TECHNIQUE:
You need the routines and global declarations below. Everywhere in your
program that you Reset or Rewrite a file "f" for the first time, insert an
"OpenExtend(f);" invocation immediately after the Reset or Rewrite. At every
place in your program that you call one of Turbo's built-in routines (other
than Assign) for handling files (e.g., Read, Write, Close, Seek, Reset,
Rewrite, etc.), put an "UnExtend(f);" invocation immediately prior to the call,
and a "ReExtend(f);" invocation immediately after the call. Do not, however,
insert UnExtend and ReExtend calls around the very first Reset or Rewrite that
you use to initially open a file.
At the very top of your program, prior to the "program" statement, put the
compiler directive {$F252}. (You may use a value smaller than 252, if you
wish. Under DOS 2.0/2.1, values above 96 have no additional benefit. Each
larger value for the {$Fnnn} directive uses 2 additional bytes in the program's
global data space.) The value you specify for the {$Fnnn} directive is the
maximum number of files you will be able to have open at the same time in your
program.
Edit your CONFIG.SYS file (see the DOS manual for details), so that it includes
a line that says "FILES=nnn". The value of "nnn" should be 3 greater than the
value you specified for the {$Fnnn} directive (larger values will provide no
additional benefit, with respect to your individual program), and should not
exceed 99 (for DOS 2.0/2.1) or 255 (for DOS 3.0 and higher). Under any version
of DOS, the minimum allowable value for "FILES=nnn" is 8. Then, reboot your
computer so that the "FILES=nnn" parameter takes hold. That's it!
Note: EXTEND.PAS uses a typed constant whose value changes during the course of
execution. Consequently, when compiling any program that uses EXTEND.PAS you
must either: 1) compile the program in memory and run it only in memory, or 2)
compile the program to a .COM file, and execute the .COM file. The program
will bomb if you compile and run the program in memory, and then immediately
compile to a .COM file without first leaving and restarting Turbo.
Running the sample program below will tell you the maximum number of files you
can have open at once (usually 96 or 252, unless you have chosen a smaller
value for the {$Fnnn} directive, or "FILES=nnn" has a value less than the
maximum, or a resident program has files open, or there are some orphaned files
due to previous program error). You will need to remove a "comment" line to
run the program...see the instructions below. This program takes a while to
run, due to the heavy disk I/O, so running it on a hard disk (or, even better,
a RAM disk) is recommended. Make sure that you are running the program in a
subdirectory, so that you don't run up against the DOS limit on the number of
allowable files in the root directory of a drive. Enjoy!
Note: Turbo/DOS normally closes all open files when a program aborts due to a
run-time error or I/O error (though some data in Turbo Text output files will
normally be lost in such cases, anyway). The "Extend" technique presented here
defeats this automatic-close mechanism. This can leave lost clusters on the
disk, and can cause the DCB Table to fill up (see below), if a run-time error
or I/O error occurs while "extended" files are open. These are not serious
conditions, though you may have to reboot the computer if a subsequent program
aborts with not enough available file handles. Also, the lost sectors should
be occasionally excised using "CHKDSK /F". To avoid these two problems,
implement a user-written error handler that will capture all run-time errors
and I/O errors, and allow a graceful exit by closing all of the "extended"
files before exiting to DOS. For details on writing an error handler, see the
Turbo manual or the README file on the Turbo distribution disk.
THE TECHNICAL DETAILS:
Much of the following information is not documented in the DOS Technical
Reference manual.
Under DOS 1.0 and 1.1, all files were accessed via File Control Blocks (FCB's).
There was no limit to the number of FCB's that a program could use, so there
was no limit to the number of files open simultaneously.
Under DOS 2.0 and higher, an alternate (and preferable) method of accessing
files was introduced, using a 2-byte integer called a "handle" to refer to a
file. A "handle" file is described using a data structure called a Device
Control Block (DCB). However, DOS provides the storage space for all DCB's,
rather than having the application program store the DCB's, so the number of
available DCB's is limited to the amount of space that DOS has set aside for
them. The maximum number of files/devices that can be open simultaneously, on
the whole computer, is the number of slots available in the DCB Table created
by DOS. The DCB's in the DCB Table are consecutively numbered, starting with
0. DCB's 0, 1, and 2 are predefined by DOS to correspond to the AUX, CON, and
PRN devices, respectively. All remaining DCB's in the DCB Table are available
for files or devices used by application programs.
So that I/O redirection can be supported, the DCB numbers are not used directly
when accessing files. Instead, a file "handle" is used. A "handle" is an
index into a 20-byte array, called the Handle Table, located at offset 18H of
the Program Segment Prefix (PSP) for a program. (For a general discussion of
the PSP, see the DOS Technical Reference manual). Each element of the Handle
Table is the DCB number of a file or device. The value at index "handle" in
the Handle Table is the DCB number of the file or device that implements that
file handle. The handles are numbered from 0 to 19. Thus, if the value 8 is
in the 6th byte of the Handle Table, the handle "5" refers to the file (or
device) described by the DCB in slot 8 of the DCB Table (remember that handles
are numbered starting at zero). If a handle is not currently being used, its
entry in the Handle Table is FFH. DOS predefines the first 5 handles to be
Stdin, Stdout, Stderr, Stdaux, and Stdprn, so the first 5 entries in the Handle
Table are 1, 1, 1, 0, and 2, corresponding to the DCB numbers for the CON (1),
AUX (0), and PRN (2) devices. This leaves only 15 available handles for
opening files (or new devices).
Every time a new handle file is opened, a new handle gets used, and the handle
is freed when the handle file is closed. Since there are only 20 slots
available in the Handle Table for a program, DOS only allows a "process" to
have a maximum of 20 file handles in use simultaneously (and the first 5
entries are predefined, as just noted, unless those handles get closed and
reused). Every new handle file requires a unique handle, so only 20
files/devices can be open at the same time by a single process (unless FCB's
are used). (A "process" is any program that has been loaded into memory by
DOS. There may be several processes in memory at once, if a multitasker is
being used, or if one program invokes a child program, etc.) There can be many
more than 20 DCB's in the DCB Table, so the real limitation, for each process,
is the size of the Handle Table in the program's PSP.
The size of the DCB Table (i.e., the maximum number of files/devices that can
be open simultaneously in the whole computer) is controlled by the "FILES=nnn"
entry in the CONFIG.SYS file. The minimum number of slots is 8. Under DOS
2.0/2.1, the maximum number is 99, and under DOS 3.0 and higher, the maximum is
255. As previously mentioned, the first three of these DCB slots are occupied
by the AUX, CON, and PRN devices.
A single program can use all of the DCB's in the DCB Table (except for the 3
reserved by DOS) all on its own, by effectively bypassing the Handle Table in
the PSP, except on a temporary basis. Instead of allowing DOS to store the DCB
numbers in the Handle Table, the program can store these numbers elsewhere.
Then, to manipulate a file using DOS, the program can temporarily put the DCB
number of that file into a slot in the Handle Table, pass the index of that
table slot (i.e., that "handle") to DOS, and DOS will operate on that
handle/DCB number. After the DOS call, the program can remove that DCB number
from the designated Handle Table slot, freeing up that handle for use in
another DOS call for another file. In this way, DOS can be fooled into
accessing up to 96 (or 252) different files/devices using a single handle entry
in the Handle Table, because the only limit is the size of the DCB Table.
The Handle Table slot that one decides to use, in the above scheme, might
already be in use. I.e., the slot might contain a value other than FFH,
indicating that it holds the actual DCB number of another open file. One can
alleviate the conflict by saving that DCB number in a temporary variable before
temporarily using that Handle Table slot to access DOS, then restore the DCB
number to the slot afterward.
The scheme outlined above, for keeping more than 20 files open at the same time
in a single program, will work fine in an assembly language program. However,
there is one remaining complication when using the technique with Turbo Pascal.
Turbo maintains its own, internal list of all open handles at run-time, which
we will call the Open Handle List. Turbo adds a handle to the list whenever a
program opens a file using Reset or Rewrite, and then Turbo removes the handle
from the list whenever a program does a Close.
Turbo maintains the Open Handle List for the following reason: if a Turbo
program terminates normally to DOS, DOS will automatically close all open
handles. (Don't depend on this feature, though, for Text output files. Turbo
maintains a Text buffer in RAM whose contents will be lost if the Text file is
not closed using Turbo's Close routine.) However, if one invokes a Turbo
program "in memory," directly from Turbo's main menu, DOS is not able to close
the open handles upon termination (because Turbo itself is still running, so
DOS doesn't know that the "in memory" Turbo program terminated). For this
reason, Turbo takes over the duty of closing all open handles when a Turbo
program terminates. To do so, Turbo maintains the Open Handle List.
The maximum size of the Open Handle List is set by the rarely-used {$Fnnn}
compiler directive that Turbo provides. Thus, {$F20} sets aside space for 20
open handles on the list. If you attempt to open a file in a normal Turbo
program that does not use the "Extend" technique outlined in this document, and
you get an I/O error F3 ("Too many open files" -- not documented in early
editions of the Turbo 3.0 manual), it can mean either: (1) there are no more
available handles in the Handle Table of the PSP; (2) there are no more
available DCB's in the global DCB Table; or (3) Turbo's Open Handle List is
full. In practice, I/O error F3 usually means (1), because most people have
(or should have) at least FILES=20 in their CONFIG.SYS file (so the DCB Table
should still have some available entries), and the default value for the
{$Fnnn} directive is 16 (which is greater than the 15 handles that are normally
available in the Handle Table).
The Open Handle List injects the following complication into the "Extend"
scheme presented above: When Reset or Rewrite is used to open a Turbo file,
Turbo adds that handle to the Open Handle List, as noted above. We then free
up that Handle Table slot by copying the DCB number in that slot to another
location, and store FFH into that slot. Now, when we want to access the file,
we are free to temporarily put that DCB number into any Handle Table slot,
perform the file access, and then restore any previous DCB number that may have
been in that slot. However, when we Close the file, we must be sure to use
exactly the same Handle Table slot as was used when the file was opened. This
is because, when we execute the Turbo Close routine, Close must "see" the same
handle as was used to open the file, so that it can successfully remove that
handle from Open Handle List. If we do not use the same Handle Table slot when
we Close the file, the original handle will not get removed from the Open
Handle List, and the Open Handle List will start to fill up. Eventually, if
files keep getting opened and closed, a Rewrite or Reset will result in an I/O
error F3 (because the Open Handle List is full), even though there may be
available slots in both the Handle Table and the DCB Table.
In the "Extend" technique proposed here, we will always use the same Handle
Table slot when accessing a given Turbo file, though several Turbo files may
share the same Handle Table slot. The scheme works as follows: After a Reset
or Rewrite that opens a Turbo file, Turbo stores the file handle as an integer
value in the first two bytes of the FIB that Turbo uses to represent the file.
In truth, the high-order byte of this handle will always be zero, because
(currently) the only handle numbers that DOS supports are 0-19. Thus, when we
are not using an "extended" file, we free up its Handle Table slot by copying
the DCB number for that file into the second byte (the high-order byte) of the
handle stored in the FIB. When we want to access the file, we look again at
the first two bytes of the FIB. We copy the DCB number (stored in the second
byte of the FIB) into the appropriate Handle Table slot (stored in the first
byte of the FIB), zero out the second byte of the FIB (the high-order byte of
the handle), and then invoke the desired Turbo file access routine(s). When we
are through invoking those routines, we reverse the process, freeing up that
Handle Table slot again. Since a given "extended" file is always accessed via
the same Handle Table slot, Turbo's Open Handle List works correctly, and the
spurious I/O error F3 is averted.
The OpenExtend, UnExtend, and ReExtend routines below use this technique.
OpenExtend(f) is used on a previously-opened file, "f." It moves f's DCB
number into f's FIB, and stores an FFH into f's slot in the Handle Table.
UnExtend(f) copies the current DCB number (if any) in f's slot of the Handle
Table to a safe place, copies the DCB number of "f" to that slot, and
zeroes-out the DCB number in the FIB, in preparation for its use by Turbo/DOS.
ReExtend(f) moves f's DCB number back into the FIB, and restores the previous
value (if any) of that slot in the Handle Table.
To obtain the address of the Handle Table, which is at offset 18H in the PSP,
the program needs to find the address of its PSP. Normally, this is very easy:
when DOS loads a .COM file, the address of the PSP is just CS:0000. Using
CS:0000 in this manner would be viable as long as we compile the program to a
.COM file and execute the .COM file. However, if we run the program in memory,
from the Turbo menu, Turbo makes a copy of its own PSP for use by the program.
DOS still thinks of Turbo's PSP as being the "official" PSP for the running
program, since DOS did not "see" Turbo invoke the program. Hence, CS:0000 is
not the valid PSP address when the program is running in memory, since that is
the address of the program's "fake" PSP rather than Turbo's PSP.
To allow the program to work correctly both when running in memory and when run
as a .COM file, we use the DOS function call 62H, "Get Program Segment Prefix
Address (PSP)." This function call is available in DOS 3.0 and higher. There
is an identical function call in DOS 2.0/2.1, but its function number is 51H,
and it is not documented. Function 51H is also available in DOS 3.0/3.1.
However, for upward-compatibility reasons with future versions of DOS, we will
use the undocumented 51H function with DOS 2.0/2.1 (since we know 51H is
available in those versions of DOS), and use 62H for DOS 3.0 and higher (since
62H is a documented function). There is no such function call in DOS 1.0/1.1,
but the technique below will not work with those early versions of DOS anyway,
since they did not provide file handles. To decide whether to use function 51H
or 62H, we call DOS function 30H, "Get DOS Version Number," to determine which
version of DOS is running. This strategy for obtaining the Handle Table
address is implemented in the GetHandleTableAddr function, below, which gets
called only once (the first time that OpenExtend is called).
Note: The "Extend" technique presented here will not interfere with overlays in
your program (since it only commandeers a Handle Table slot temporarily),
provided that your program leaves at least one DCB available for use by the
Turbo run-time library to read in overlay files.
Many thanks to Bela Lubkin (CompuServe 73047,1112) for masterminding this idea,
to Kim Kokkonen (CompuServe 72457,2131) for helping me debug it, and to Scott
Bussinger (CompuServe 72247,2671), who discovered and fixed the insidious
interaction with Turbo's Open Handle List. For more discussion of Handle
Tables and the implementation of DOS redirection, please see Stan Mitchell,
"Command Line Redirection," PC Tech Journal, January 1986, Page 44.
Change Log:
Version 1.0: Original release.
Version 1.1: Changed to invoke a DOS function to get the PSP address, so that
programs using this technique can be run in memory under Turbo.
Version 1.2: Replaced some outdated comments.
Version 1.3: Changed example program so that it erases the files it creates.
Version 1.4: Added a caveat about compiling programs that use EXTEND.PAS.
Version 1.5: Fixed the interaction with Turbo's Open Handle List, which
previously causes spurious F3 I/O errors. Added a warning that
EXTEND disables the Turbo/DOS automatic file-close feature, and
how to work around it.
******************************************************************************)
{$F252}
const
LastHandle = 19; {Highest-numbered handle}
UnusedHandle = $FF; {DcbTable entry that denotes an unused handle}
type
HandleTable = array[0..LastHandle] of Byte;
HandleTablePtr = ^HandleTable;
const
TablePtrOk: Boolean = false; {"True" iff TablePtr is initialized}
var
TablePtr: HandleTablePtr; {Points to Handle Table for this process}
SaveDcb: Byte; {Temporary variable for a DCB number during a function call}
{Internal routine. Returns the address of the Handle Table, which is at offset
18H in the PSP.}
function GetHandleTableAddr: HandleTablePtr;
var
regs: record
case Integer of
1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
end;
begin
regs.AH := $30;
MsDos(regs); {Get DOS version number}
case regs.AL of
{If running under a DOS earlier than 2.0, force a run-time error}
0: regs.AL := regs.AL div 0;
2: regs.AH := $51; {Undocumented, but works with DOS 2.0/2.1 (and 3.X)}
else regs.AH := $62 {Works with DOS 3.0 and higher}
end;
MsDos(regs); {Get PSP address}
GetHandleTableAddr := Ptr(regs.BX, $18)
end {GetHandleTableAddr};
{Causes "f" to become an "extended" file; i.e., to remain open without using up
any file handles. The parameter "f" must be any Turbo file; e.g., a File, a
File of Byte, a File of Foo, Text, etc. This routine should be called
immediately after the Reset or Rewrite that is initially used to open "f."
After "f" has become extended, it cannot be used by Turbo's built-in file
routines until it has been unextended using UnExtend.}
procedure OpenExtend (var f);
var
fib: record handle, dcbnum: Byte end absolute f;
begin
if not TablePtrOk then
begin
TablePtr := GetHandleTableAddr;
TablePtrOk := true
end;
{If the "dcbnum" byte is already in use, a futuristic DOS must be running
that allows more than 255 handles in a single process. In this case, force
a run-time error.}
if fib.dcbnum <> 0 then fib.dcbnum := fib.dcbnum div 0;
fib.dcbnum := TablePtr^[fib.handle];
TablePtr^[fib.handle] := UnusedHandle
end {OpenExtend};
{Unextends the extended file "f," so that it can be used by any of Turbo's
built-in file routines. Note that "f" must have been converted to an extended
file by OpenExtend before invoking UnExtend(f). After calling UnExtend, and
then invoking the Turbo file function on "f," ReExtend(f) should be invoked
immediately to re-extend "f" and restore the DOS file state information.}
procedure UnExtend (var f);
var
fib: record handle, dcbnum: Byte end absolute f;
begin
SaveDcb := TablePtr^[fib.handle];
TablePtr^[fib.handle] := fib.dcbnum;
fib.dcbnum := 0
end {UnExtend};
{Re-extends "f" into an extended file. Note that "f" must have been converted
to an extended file by OpenExtend, and then unextended using UnExtend, before
"f" can re-extended using ReExtend. ReExtend(f) should be invoked immediately
after any normal Turbo file function call using "f."}
procedure ReExtend (var f);
var
fib: record handle, dcbnum: Byte end absolute f;
begin
fib.dcbnum := TablePtr^[fib.handle];
TablePtr^[fib.handle] := SaveDcb
end {ReExtend};
{Example program -- remove the "(*" line below to enable the program (a Bela
Lubkin trick). This program opens as many Text files as it can, until DOS
runs out of room in its DCB Table. It then reports how many files were
successfully opened, writes a line to each of them, then closes and erases
each of them. Note: The value of the "FILES=nnn" parameter in the CONFIG.SYS
file must be set to an appropriate value (see above). If you change the
"FILES=nnn" value, be sure to reboot before running this program.
This program takes a while to run, due to the heavy disk I/O, so running it on
a hard disk (or, even better, a RAM disk) is recommended. Make sure that you
are running the program in a subdirectory, so that you don't run up against
the DOS limit on the number of allowable files in the root directory of a
drive.}
(*
const
MaxCount = 255;
var
num: string[6];
f: array[1..MaxCount] of Text;
i, count: Integer;
result: Byte;
begin
writeln('Opening files...');
i := 0;
repeat
i := i + 1;
Str(i, num);
Assign(f[i], 'junk' + num + '.txt');
{$I-} Rewrite(f[i]); {$I+}
result := IOResult;
if result = 0 then OpenExtend(f[i])
until result <> 0;
count := i - 1;
writeln('Successfully opened ', count, ' files at the same time. ',
'Writing to each file...');
for i := 1 to count do
begin
UnExtend(f[i]);
writeln(f[i], 'This is a test');
ReExtend(f[i])
end;
writeln('Closing and erasing each file...');
for i := 1 to count do
begin
UnExtend(f[i]);
Close(f[i]);
Erase(f[i]);
ReExtend(f[i])
end;
writeln('Done.')
end.
(**)